Messaging Patterns in ao
This reference guide explains the messaging patterns available in ao and when to use each one.
Quick Reference: Choosing the Right Pattern
If you need to... | Process Flow | Key function(s) |
---|---|---|
Send a message without waiting for a response | A → B | ao.send |
Send a message and wait for a response | A → B → A | ao.send().receive() |
Process messages and respond to the sender | B → A | Handlers.add + msg.reply |
Create a chain of processing services | A → B → C → A | msg.forward + ao.send().receive() |
Wait for any matching message regardless of sender | Any → A | Receive (capital R) |
Create a standard automated response | B → A | Handlers.utils.reply |
Sending Messages
ao.send
: Asynchronous Message Sending
Non-blocking direct A → B messaging that returns immediately after sending.
- Use for fire-and-forget notifications or starting async conversations
- Returns a promise-like object that can be chained with
.receive()
if needed - Good for parallel processing since it doesn't block execution
Client (A) → Service (B)
↓ ↓
Continues Processes
execution message
Basic Send Example:
-- Non-blocking send example
local serviceId = "process-789" -- Process ID of the target service
ao.send({
Target = serviceId,
Data = "Hello!",
Action = "Greeting"
})
-- Code here runs immediately after sending
msg.reply
: Asynchronous Response Sending
Non-blocking B → A response with automatic reference tracking. Used within handlers to respond to incoming messages.
- Automatically links response to original message via
X-Reference
- Enables asynchronous request-response patterns
- Automatically sets
Target
to the original sender orReply-To
address if specified
Client (A) → Service (B)
←
Response tagged with X-Reference
Handler Reply Example:
-- Non-blocking reply in a handler
Handlers.add("greeting-handler",
{ Action = "Greeting" },
function(msg)
msg.reply({ Data = "Hi back!" }) -- Returns immediately
-- Handler continues executing here
end
)
msg.forward
: Message Forwarding
Non-blocking multi-process routing for A → B → C → A patterns. Creates a sanitized copy of the original message.
- Takes a
target
and a partial message to overwrite forwarded message fields - Preserves
Reply-To
andX-Reference
properties for complete message tracking - Sets
X-Origin
to original sender, enabling final service to reply directly to originator
Client (A) → Service (B) → Backend (C)
↖ ↙
Response with X-Reference
Multi-Process Pipeline Example:
-- In client process
local middlewareProcessId = "process-123"
local finalProcessId = "process-456"
-- Send to middleware and wait for response from final service
local response = ao.send({
Target = middlewareProcessId,
Action = "Transform",
Data = "raw-data"
}).receive(finalProcessId) -- Explicitly wait for response from final service
-- In middleware service
Handlers.add("transform-middleware",
{ Action = "Transform" },
function(msg)
local finalProcessId = "process-456"
msg.forward(finalProcessId, {
Data = msg.Data .. " (pre-processed)",
Action = "Transform-Processed"
})
end
)
-- In final service
Handlers.add("final-processor",
{ Action = "Transform-Processed" },
function(msg)
-- No need to know the client ID - it's stored in X-Origin
msg.forward(msg['X-Origin'], {
Data = msg.Data .. " (final processing complete)",
Action = "Transform-Complete"
})
end
)
Handlers.utils.reply
: Simple Reply Handler Creation
Creates a handler function that automatically replies with a fixed response. A wrapper around msg.reply
for common use cases.
Simple String Response Example:
-- Simple string response handler
Handlers.add("echo-handler",
{ Action = "Echo" },
Handlers.utils.reply("Echo reply!")
)
-- Equivalent to:
Handlers.add("echo-handler",
{ Action = "Echo" },
function(msg)
msg.reply({ Data = "Echo reply!" })
end
)
Message Table Response Example:
-- Message table response handler
Handlers.add("status-handler",
{ Action = "Status" },
Handlers.utils.reply({
Data = "OK",
Action = "Status-Response"
})
)
Receiving Messages
Receive
(Capital R): Blocking Pattern Matcher
Blocks execution until any matching message arrives from any sender. Under the hood, this is implemented using Handlers.once
, making it a one-time pattern matcher that automatically removes itself after execution.
- Waits for any message matching the pattern, regardless of origin
- Use for synchronous message processing flows or event listening
- Automatically removes the handler after first match (using
Handlers.once
internally)
Process (A)
↓
Blocks until match received
↓
Continues execution
Message Pattern Matching Example:
-- Blocks until matching message received
local msg = Receive({
Action = "Update"
})
if msg then
-- Process message
end
ao.send().receive
(Lowercase r): Blocking Reference Matcher
Blocks execution until a specific reply arrives, enabling A → B → A and A → B → C → A request-response cycles.
- Only matches messages linked by
X-Reference
- Can specify a target process ID to indicate which process will reply
- Implicitly waits for the proper response based on message reference chains
- For A → B → A flows, process B uses
msg.reply
- For A → B → C → A flows, processes B and C use
msg.forward
Basic Request-Response Example:
-- Basic usage: wait for reply from target
local serviceId = "process-789"
local reply = ao.send({
Target = serviceId,
Action = "Query",
Data = { query: "select" }
}).receive() -- Blocks until response received
Message Properties
The following properties track message chains and ensure proper routing:
Reference
: Unique identifier automatically assigned to each message.Reply-To
: Specifies the destination for responses.X-
: Any property starting withX-
denotes a 'forwarded' tag and is automatically managed by the system.X-Reference
: Maintains the conversation chain across replies and forwards.X-Origin
: Tracks the conversation originator.
The system automatically manages these properties when using msg.reply
and msg.forward
. Check out the source code to see exactly how these properties are managed.
Blocking vs. Non-Blocking
Functions either pause your code or let it continue running:
- Non-blocking (
ao.send
,msg.reply
,msg.forward
): Send and continue execution - Blocking (
Receive
,.receive()
): Pause until response arrives